{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lesson 19 - Modules and Classes\n",
    "\n",
    "Readings:\n",
    "\n",
    "* Shaw Ex40-52 (most important: Ex40-45)\n",
    "* https://docs.python.org/3.6/tutorial/modules.html\n",
    "* https://docs.python.org/3.6/tutorial/classes.html\n",
    "* https://www.learnpython.org/en/Modules_and_Packages\n",
    "\n",
    "Topics covered:\n",
    "\n",
    "* [Modules](#modules)\n",
    "* [Classes](#classes)\n",
    "* [Object-oriented nomenclature](#oop)\n",
    "* [Inheritance and composition](#inheritance)\n",
    "* [Coding style (including PEP 8)](#style)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"modules\"></a>\n",
    "\n",
    "### Modules\n",
    "\n",
    "A **module** is a file containing Python definitions and statements. The file name is the module name with the suffix `.py` appended. Definitions from a module can be imported into other modules or into the main module (the collection of variables that you have access to in a script executed at the top level and in calculator mode)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Using an example from the [Python Docs](https://docs.python.org/3.6/tutorial/modules.html), we can use our favorite text editor to create a file called `fibo.py` in the current directory with the following contents:\n",
    "\n",
    "```python\n",
    "# Fibonacci numbers module\n",
    "\n",
    "def fib(n):    # write Fibonacci series up to a max value of n\n",
    "    a, b = 0, 1\n",
    "    while b < n:\n",
    "        print(b, end=' ')\n",
    "        a, b = b, a + b\n",
    "    print()\n",
    "\n",
    "def fib2(n):   # return Fibonacci series up to a max value of n\n",
    "    result = []\n",
    "    a, b = 0, 1\n",
    "    while b < n:\n",
    "        result.append(b)\n",
    "        a, b = b, a + b\n",
    "    return result\n",
    "```\n",
    "\n",
    "Now enter the IPython (or Python) interpreter and import this module with the following command:\n",
    "\n",
    "```python\n",
    ">>> import fibo\n",
    "```\n",
    "\n",
    "This does not enter the names of the functions defined in `fibo` directly in the current symbol table; it only enters the module name `fibo` there. Using the module name you can access the functions:\n",
    "\n",
    "```python\n",
    ">>> fibo.fib(1000)\n",
    "1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987\n",
    ">>> fibo.fib2(100)\n",
    "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]\n",
    ">>> fibo.__name__\n",
    "'fibo'\n",
    "```\n",
    "\n",
    "If you intend to use a function often you can assign it to a local name:\n",
    "\n",
    "```python\n",
    ">>> fib = fibo.fib\n",
    ">>> fib(500)\n",
    "1 1 2 3 5 8 13 21 34 55 89 144 233 377\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Variants of the module import statement\n",
    "\n",
    "We have seen all of these variants of the module import statement before. What does each of these commands do?\n",
    "\n",
    "```python\n",
    ">>> from fibo import fib, fib2\n",
    "\n",
    ">>> from fibo import *\n",
    "\n",
    ">>> import fibo as fib\n",
    "\n",
    ">>> from fibo import fib as fibonacci\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Importing from inside Jupyter notebook\n",
    "\n",
    "It works the same as from the Terminal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import fibo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 1 2 3 5 8 13 21 \n"
     ]
    }
   ],
   "source": [
    "fibo.fib(30)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 1, 2, 3, 5, 8, 13, 21, 34]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fibo.fib2(50)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Executing modules as scripts\n",
    "\n",
    "When you run a Python module with\n",
    "\n",
    "```\n",
    "$ python fibo.py <arguments>\n",
    "```\n",
    "\n",
    "the code in the module will be executed, just as if you imported it, but with the `__name__` set to `\"__main__\"`. That means that by adding this code at the end of your module:\n",
    "\n",
    "```python\n",
    "if __name__ == \"__main__\":\n",
    "    import sys\n",
    "    fib(int(sys.argv[1]))\n",
    "```\n",
    "\n",
    "you can make the file usable as a script as well as an importable module, because the code that parses the command line only runs if the module is executed as the \"main\" file:\n",
    "\n",
    "```\n",
    "$ python fibo.py 50\n",
    "1 1 2 3 5 8 13 21 34\n",
    "```\n",
    "\n",
    "If the module is imported, the code is not run:\n",
    "\n",
    "```python\n",
    ">>> import fibo\n",
    ">>>\n",
    "```\n",
    "\n",
    "This is often used either to provide a convenient user interface to a module, or for testing purposes (running the module as a script executes a test suite)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Packages\n",
    "\n",
    "Packages are a way of structuring Python’s module namespace by using \"dotted module names\". For example, the module name `A.B` designates a submodule named `B` in a package named `A`. Just like the use of modules saves the authors of different modules from having to worry about each other’s global variable names, the use of dotted module names saves the authors of multi-module packages like NumPy or the Python Imaging Library from having to worry about each other's module names.\n",
    "\n",
    "For example, `pyplot` is a submodule of the `matplotlib` module, which we use all the time:\n",
    "\n",
    "```python\n",
    "import matplotlib.pyplot as plt\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"classes\"></a>\n",
    "\n",
    "### Classes\n",
    "\n",
    "Classes provide a means of bundling data and functionality together. Creating a new **class** creates a new *type* of object, allowing new *instances* (or *tokens*) of that type to be made. Each class instance can have **attributes** attached to it for maintaining its state. Class instances can also have **methods** (defined by its class) for modifying its state.\n",
    "\n",
    "Put in philosophical terms, *object class* is to *object instance* as *type* is to *token*. For example, many of you have a MacBook (a *class* or *type* of computer), but the physical object you possess is a unique computer (an *instance* or *token* of a MacBook).\n",
    "\n",
    "Classes are used when you want to create many objects that all have the same properties, and each one won't interfere with the others. A module is typically imported only once for the entire program, but a module can contain classes.\n",
    "\n",
    "Take this simple example (adapted from Shaw's *Learn Python The Hard Way*), which we'll save as `myclass.py`:\n",
    "\n",
    "```python\n",
    "class MyClass(object):\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.walrus = \"I am the walrus\"\n",
    "        \n",
    "    def choo(self):\n",
    "        print(\"Goo goo g'joob.\")\n",
    "```\n",
    "\n",
    "Then import the module and create (instantiate) a new object of type `MyClass`:\n",
    "\n",
    "```\n",
    ">>> import myclass\n",
    ">>> paul = myclass.MyClass()\n",
    ">>> paul.walrus\n",
    "'I am the walrus'\n",
    ">>> paul.choo()\n",
    "Goo goo g'joob.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You *instantiate* (create) a class by calling the class like it's a function. When you intantiate an object from a class (here: `MyClass`), Python does the following things:\n",
    "\n",
    "1. Python looks for `MyClass()` and sees that it is a class you’ve defined.\n",
    "2. Python crafts an empty object with all the functions you’ve specified in the class using `def`.\n",
    "3. Python then looks to see if you made a \"magic\" `__init__` function, and if you have it calls that function to initialize your newly created empty object.\n",
    "4. In the `MyClass` function `__init__` you then get this extra variable self, which is that empty object Python made for you, and you can set variables on it just like you would with a module, dictionary, or other object.\n",
    "5. In this case, you set `self.walrus` to a song lyric and then you've initialized this object.\n",
    "6. Now Python can take this newly minted object and assign it to the `paul` variable for you to work with."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is another example, which we'll save as `song.py`:\n",
    "\n",
    "```python\n",
    "class Song(object):\n",
    "\n",
    "\tdef __init__(self, lyrics):\n",
    "\t\tself.lyrics = lyrics\n",
    "\n",
    "\tdef print_it(self):\n",
    "\t\tfor line in self.lyrics:\n",
    "\t\t\tprint(line)\n",
    "```\n",
    "\n",
    "Then import the module and create (instantiate) some `Song` objects:\n",
    "\n",
    "```\n",
    ">>> from song import *\n",
    ">>> lyrics1 = [\"I'd like to be under the sea\", \n",
    "...            \"In an octopus's garden with you.\"]\n",
    ">>> lyrics2 = ['Oops, I did it again.', \n",
    "...            'Hit me, baby, one more time.']\n",
    ">>> song1 = Song(lyrics1)\n",
    ">>> song2 = Song(lyrics2)\n",
    ">>> song1.print_it()\n",
    "I'd like to be under the sea\n",
    "In an octopus's garden with you.\n",
    ">>> song2.print_it()\n",
    "Oops, I did it again.\n",
    "Hit me, baby, one more time.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"oop\"></a>\n",
    "\n",
    "### Object-oriented nomenclature\n",
    "\n",
    "From Shaw's *Learn Python The Hard Way* Exercise 41.\n",
    "\n",
    "#### Word Drills\n",
    "\n",
    "**class** Tell Python to make a new type of thing.\n",
    "\n",
    "**object** Two meanings: the most basic type of thing, and any instance of some thing.\n",
    "\n",
    "**instance** What you get when you tell Python to create a new object from a type of class.\n",
    "\n",
    "**def** How you define a function inside a class.\n",
    "\n",
    "**self** Inside the functions in a class, self is a variable for the instance/object being accessed.\n",
    "\n",
    "**inheritance** The concept that one class can inherit traits from another class, much like you and your parents.\n",
    "\n",
    "**composition** The concept that a class can be composed of other classes as parts, much like how a car has wheels.\n",
    "\n",
    "**attribute** A property classes have that are from composition and are usually variables.\n",
    "\n",
    "**method** A property classes have that are from composition and are another name for functions.\n",
    "\n",
    "**is-a** A phrase to say that something inherits from another, as in a \"salmon\" is-a \"fish\".\n",
    "\n",
    "**has-a** A phrase to say that something is composed of other things or has a trait, as in \"a salmon has-a mouth\".\n",
    "\n",
    "#### Phrase Drills\n",
    "\n",
    "**class X(Y)** \"Make a class named X that is-a Y.\"\n",
    "\n",
    "**class X(object): def __init__(self, J)** \"class X has-a `__init__` that takes self and J parameters.\"\n",
    "\n",
    "**class X(object): def M(self, J)** \"class X has-a function named M that takes self and J parameters.\" \n",
    "\n",
    "**foo = X()** \"Set foo to an instance of class X.\"\n",
    "\n",
    "**foo.M(J)** \"From foo get the M function, and call it with parameters self, J.\"\n",
    "\n",
    "**foo.K = Q** \"From foo get the K attribute and set it to Q.\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### About `class Name(object)`\n",
    "\n",
    "Whenever we create a class, for example a class called `Name`, we must write `class Name(object)`. `object` is itself a class, and every class we create inherits the properties of this class."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"inheritance\"></a>\n",
    "\n",
    "### Inheritance and composition\n",
    "\n",
    "**Inheritance** is used to indicate that one class will get most or all of its features from a parent class. This happens implicitly whenever you write `class Foo(Bar)`, which says \"Make a class Foo that inherits from Bar.\" When you do this, the language makes any action that you do on instances of `Foo` also work as if they were done to an instance of `Bar`. Doing this lets you put common functionality in the `Bar` class, then specialize that functionality in the `Foo` class as needed.\n",
    "\n",
    "For example, observe what happens with the following code, this time executed directly in our IPython notebook:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Parent(object):\n",
    "\n",
    "    def implicit(self):\n",
    "        print(\"PARENT implicit()\")\n",
    "\n",
    "class Child(Parent):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "dad = Parent()\n",
    "son = Child()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "PARENT implicit()\n",
      "PARENT implicit()\n"
     ]
    }
   ],
   "source": [
    "dad.implicit()\n",
    "son.implicit()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The above is called *implicit inheritance*. It is possible to override a function in the parent class by using the same function name in the child class, or to switch between the two versions of the function name (see Shaw Exercise 44)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Composition** refers to composing your classes using functions from other classes, rather than relying on implicit inheritance, to arrive at the same result we just saw with inheritance.\n",
    "\n",
    "For example, we can achieve the same thing as above using functions of other classes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Other(object):\n",
    "    \n",
    "    def implicit(self):\n",
    "        print(\"OTHER implicit()\")\n",
    "\n",
    "class Child(object):\n",
    "    \n",
    "    def __init__(self):\n",
    "        self.other = Other()\n",
    "    \n",
    "    def implicit(self):\n",
    "        self.other.implicit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "son = Child()\n",
    "stepson = Other()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "OTHER implicit()\n",
      "OTHER implicit()\n"
     ]
    }
   ],
   "source": [
    "son.implicit()\n",
    "stepson.implicit()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Inheritance or composition?** Both inheritance and composition are designed to prevent re-use of code, which is unclean and inefficient. Inheritance solves this problem by creating a mechanism for you to have implied features in base classes. Composition solves this by giving you modules and the ability to call functions in other classes.\n",
    "\n",
    "Use *composition* to package code into modules that are used in many different unrelated places and situations.\n",
    "\n",
    "Use *inheritance* only when there are clearly related reusable pieces of code that fit under a single common concept or if you have to because of something you’re using."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Some practical examples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Classes: An astronomy example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can create a `Planet` class that has attributes `name` and `diameter` and methods `area()` and `volume()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Planet(object):\n",
    "    \n",
    "    def __init__(self, name, diameter):\n",
    "        self.name = name\n",
    "        self.diameter = diameter\n",
    "    \n",
    "    def area(self):\n",
    "        return(4*np.pi*(self.diameter/2)**2)\n",
    "    \n",
    "    def volume(self):\n",
    "        return(4/3*np.pi*(self.diameter/2)**3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# instantiate the class\n",
    "earth = Planet('Earth', 12742)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Earth'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "earth.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "12742"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "earth.diameter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "510064471.90978825"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "earth.area()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1083206916845.7535"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "earth.volume()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# class instantiation also supports label-based assignment\n",
    "earth = Planet(name='Earth', diameter=12742)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Inheritance with classes: An astronomy example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can create a `Moon` class that inherits the attributes and methods of `Planet`, plus an additional attribute for host planet. We do this by creating `Moon` as a subclass of `Planet`, then redefining the `__init__` function with an additional attribute for host planet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Moon(Planet):\n",
    "    \n",
    "    def __init__(self, name, diameter, host_planet):\n",
    "        Planet.__init__(self, name, diameter)\n",
    "        self.host_planet = host_planet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# instantiate the class\n",
    "moon = Moon('Moon', 3476, 'Earth')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Moon'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "moon.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3476"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "moon.diameter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Earth'"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "moon.host_planet"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "scrolled": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "37958531.99804035"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "moon.area()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "21990642870.864708"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "moon.volume()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Built-in functions with classes: A book example\n",
    "\n",
    "Below is a class called `Book` that takes values for the author, title, and the ID number. The class definition uses a number of special built-in functions to control not only how instances of the class are initialized (`__init__`) but also how they are officially represented as strings (`__repr__`), how they are informally represented as strings (`__str__`), and how equivalence operations should be evaluated (`__eq__`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Book(object):\n",
    "    \n",
    "    def __init__(self, author, title, book_id):\n",
    "        self.author = author\n",
    "        self.title = title\n",
    "        self.book_id = book_id\n",
    "\n",
    "    def __repr__(self):\n",
    "        return('Book({}, {}, {})'.format(self.author, self.title, self.book_id))\n",
    "\n",
    "    def __str__(self):\n",
    "        return('{}, {}, {}'.format(self.author, self.title, self.book_id))\n",
    "\n",
    "    def __eq__(self, other):\n",
    "        return(self.title == other.title and self.author == other.author \\\n",
    "               and self.book_id == other.book_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can instantiate an example book to see how the built-in functions affect object behavior:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "# instantiate the class\n",
    "iliad = Book('Homer', 'The Iliad', '9780140275360')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('Homer', 'The Iliad', '9780140275360')"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "iliad.author, iliad.title, iliad.book_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Book(Homer, The Iliad, 9780140275360)"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# effect of __repr__\n",
    "iliad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Book(Homer, The Iliad, 9780140275360)'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# effect of __repr__\n",
    "repr(iliad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Homer, The Iliad, 9780140275360'"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# effect of __str__\n",
    "str(iliad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Homer, The Iliad, 9780140275360\n"
     ]
    }
   ],
   "source": [
    "# which affects print commands\n",
    "print(iliad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# effect of __eq__\n",
    "iliad == Book('Homer', 'The Iliad', '9780140275360')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# only if all three are equivalent\n",
    "iliad == Book('Homer', 'The Iliad', '5')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<a id=\"style\"></a>\n",
    "\n",
    "### Coding style\n",
    "\n",
    "#### PEP 8\n",
    "\n",
    "From Wikipedia: \"Python is meant to be an easily readable language. Its formatting is visually uncluttered, and it often uses English keywords where other languages use punctuation. Unlike many other languages, it does not use curly brackets to delimit blocks, and semicolons after statements are optional. It has fewer syntactic exceptions and special cases than C or Pascal.\"\n",
    "\n",
    "The Style Guide for Python Code, also called [PEP 8](https://www.python.org/dev/peps/pep-0008/), for Python Enhancement Proposal no. 8, defines the suggested style conventions for Python code. It's a good idea to read through the PEP 8 style conventions. Here are some highlights:\n",
    "\n",
    "#### Code layout\n",
    "\n",
    "* Use 4 spaces per indentation level.\n",
    "* Limit all lines to a maximum of 79 characters.\n",
    "\n",
    "#### Functions\n",
    "\n",
    "* Programmers call functions that are part of classes \"methods\". They're the same thing.\n",
    "* Instead of naming your functions after what the function does, instead name it as if it's a command you are giving to the class. For example, `mylist.pop()` not `mylist.remove_from_end_of_list`.\n",
    "* Keep your functions small and simple.\n",
    "\n",
    "#### Classes\n",
    "\n",
    "* Your class should use \"camelcase\" like `SuperGoldFactory` rather than `super_gold_factory`.\n",
    "* Your other functions should use \"underscore format\" so write `my_awesome_hair` and not `myawe-somehair` or `MyAwesomeHair`.\n",
    "* Be consistent in how you organize your function arguments. For example, if you have one function that takes `(dog, cat, user)`, don't have another that the other takes `(user, cat, dog)`.\n",
    "* Try not to use variables that come from the module or globals.\n",
    "* Always, always have `ClassName(object)` format or else you will be in big trouble.\n",
    "\n",
    "#### Readability\n",
    "\n",
    "* Give your code vertical space (between lines) and horizontal space (between operators or commas) so people can read it.\n",
    "* If you can't read it out loud, it's probably hard to read.\n",
    "\n",
    "#### Comments\n",
    "\n",
    "* Write comments.\n",
    "* When you write comments, describe why you are doing what you are doing. The code already says how, but why you did things the way you did is more important.\n",
    "* When you write doc comments for your functions, make them for someone who will have to use your code. Give them a sentence about what someone can do with that function.\n",
    "* Keep your comments relatively short and to the point, and if you change a function, review the comment to make sure it's still correct."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
